import xlsxio
import Foundation

public enum ExcelError: Error {
    case openFailed
    case openSheetFailed
    case obtainSheetNameListFailed
}

public struct ExcelLocation: Hashable {
    public let row: Int
    public let column: Int
    
    public init(row: Int, column: Int) {
        self.row = row
        self.column = column
    }
}

public struct ExcelValue {
    public let item: String?
}

final public class ExcelSheet {
    let sheet: xlsxioreadersheet
    var values: [[ExcelValue]] = []
    
    deinit {
        xlsxioread_sheet_close(sheet)
    }
    
    init(_ reader: xlsxioreader, sheetName: String) throws {
        guard let tmpSheet = xlsxioread_sheet_open(reader, sheetName, UInt32(XLSXIOREAD_SKIP_NONE)) else {
            throw ExcelError.openSheetFailed
        }
        self.sheet = tmpSheet
        loadValue()
    }
    
    func loadValue() {
        values.removeAll()
        
        let pvalue: UnsafeMutablePointer<UnsafeMutablePointer <Int8>?> = .allocate(capacity: 1)
        var row: Int = 0
        while xlsxioread_sheet_next_row(self.sheet) > 0 {
            var column: Int = 0
            var columnValues: [ExcelValue] = []
            while xlsxioread_sheet_next_cell_string(self.sheet, pvalue) > 0 {
                if let value = pvalue.pointee {
                    columnValues.append(ExcelValue(item: String(cString: value, encoding: .utf8)))
                }
                column += 1
            }
            values.append(columnValues)
            row += 1
        }
        pvalue.deallocate()
    }
    
    /// 获取指定位置的String数据
    /// - Parameter location: 数据的横纵坐标
    public func string(of location: ExcelLocation) -> String? {
        guard location.row < values.count && location.column < values[location.row].count else { return nil }
        return values[location.row][location.column].item
    }
    
    /// 获取指定位置的ExcelValue
    /// - Parameter location: 数据的横纵坐标
    public func value(of location: ExcelLocation) -> ExcelValue? {
        guard location.row < values.count && location.column < values[location.row].count else { return nil }
        return values[location.row][location.column]
    }
    
    /// 遍历元素
    /// stop 设置true时停止遍历
    /// - Parameter body: (ExcelValue, CurrentRow, CurrentColumn, stop)
    public func forEach(_ body: (ExcelValue, Int, Int, inout Bool) throws -> Void) rethrows {
        for row in values.indices {
            let columnValues = values[row]
            for column in columnValues.indices {
                let value = columnValues[column]
                var stop: Bool = false
                try body(value, row, column, &stop)
                if (stop) { return }
            }
        }
    }
}

final public class ExcelTool {
    
    public enum HandleType {
        case read
        case write
    }
    
    let filePath: String
    let type: HandleType
    var reader: xlsxioreader!
    var readerSheets: [String: ExcelSheet] = [:]
    var readerSheetNames: [String] = []
    
    var writer: xlsxiowriter!
    
    deinit {
        readerSheets.removeAll()
        xlsxioread_close(reader)
        xlsxiowrite_close(writer)
    }
    
    public init(read path: String) throws {
        filePath = path
        type = .read
        try open(sheetName: nil)
        try loadDatas()
    }
    
    public init(write path: String, sheetName: String) throws {
        filePath = path
        type = .write
        try open(sheetName: sheetName)
        try loadDatas()
    }
    
    func open(sheetName: String? = nil) throws {
        switch type {
        case .read:
            if (reader == nil) {
                reader = xlsxioread_open(filePath)
            }
            if (reader == nil) {
                throw ExcelError.openFailed
            }
        case .write:
            writer = xlsxiowrite_open(filePath, sheetName)
            if (writer == nil) {
                throw ExcelError.openFailed
            }
        }
    }
    
    func loadDatas() throws {
        switch type {
        case .read:
            guard let sheetList = xlsxioread_sheetlist_open(reader) else {
                throw ExcelError.obtainSheetNameListFailed
            }
            while let value = xlsxioread_sheetlist_next(sheetList),
                let name = String(cString: value, encoding: .utf8) {
                self.readerSheetNames.append(name)
            }
            xlsxioread_sheetlist_close(sheetList)
        case .write:
            ()
        }
    }
    
    public func sheet(at index: Int) throws -> ExcelSheet {
        if (index >= self.readerSheetNames.count) {
            throw ExcelError.openSheetFailed
        }
        let name = self.readerSheetNames[index]
        return try sheet(of: name)
    }
    
    public func sheet(of name: String) throws -> ExcelSheet {
        guard self.readerSheetNames.contains(name) else {
            throw ExcelError.openSheetFailed
        }
        var sheet: ExcelSheet! = readerSheets[name]
        if (sheet == nil) {
            sheet = try ExcelSheet(reader, sheetName: name)
            readerSheets[name] = sheet
        }
        return sheet
    }
    
    /// 按每列顺序写入列标题, 写入后自动切换到下一行
    /// - Parameter values: 按顺序写入标题 [(标题内容: String, 列宽: Int32)]
    public func writeTitles(values: [(String, Int32)]) {
        for (value, width) in values {
            xlsxiowrite_add_column(writer, value, width)
        }
        xlsxiowrite_next_row(writer)
    }
    
    /// 按每列顺序写入数据, 写入后自动切换到下一行
    /// - Parameter values: 按顺序写入数据 [数据内容]
    public func writeDatas(values: [String]) {
        for value in values {
            xlsxiowrite_add_cell_string(writer, value);
        }
        xlsxiowrite_next_row(writer)
    }
    
}
